package com.hmjk.health.zxing.camera;

import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.Camera;
import android.hardware.Camera.AutoFocusCallback;
import android.hardware.Camera.PreviewCallback;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.SurfaceHolder;

import java.io.IOException;

import com.hmjk.health.zxing.decoding.QRScanDecoder;
import com.google.zxing.ResultPointCallback;

public final class CameraManager implements PreviewCallback, AutoFocusCallback {
	private static final String TAG = CameraManager.class.getSimpleName();
	private static final int MIN_FRAME_WIDTH = 240;
	private static final int MIN_FRAME_HEIGHT = 240;
	private static final int MAX_FRAME_WIDTH = 480;
	private static final int MAX_FRAME_HEIGHT = 480;

	private static final int TEN_DESIRED_ZOOM = 27;

	private final Context mContext;
	private Handler uiHandler;
	private Camera camera;
	private Point SR; //surfaceResolution;
	private Point CR; //cameraResolustion;
	private boolean mfocus = false;
	private QRScanDecoder mDecoder = null;
	private Rect framingRect;
	private Rect framingRectInPreview;
	private boolean previewing;
	private final boolean useOneShotPreviewCallback;
	private boolean decoding;

	public CameraManager(Context context, Handler h) {
		mContext = context;
		uiHandler = h;
	    useOneShotPreviewCallback = Integer.parseInt(Build.VERSION.SDK) > 3; // 3 = Cupcake
	}

	public void open(SurfaceHolder holder, int width, int height) throws IOException{
		if(camera == null){
			camera = Camera.open();
			if (camera == null){
		        throw new IOException();
			}
	  
			camera.setPreviewDisplay(holder);
			camera.setDisplayOrientation(90); //竖屏
	      
			Camera.Parameters p = camera.getParameters();
			SR = new Point(width, height);
			
		    Point srForCam = new Point();  
		    srForCam.x = SR.x;  
		    srForCam.y = SR.y;  
            // preview size is always something like 480*320, other 320*480  
            if (SR.x < SR.y) {  
            	srForCam.x = SR.y;  
            	srForCam.y = SR.x;  
            }  
            
			CR = getCameraResolution(p, srForCam);

			p.setPreviewSize(CR.x, CR.y);

			setZoom(p);
			camera.setParameters(p);
		}
	}
  
	private void setZoom(Camera.Parameters p) {
		String zoomSupported = p.get("zoom-supported");
		if (zoomSupported != null && !Boolean.parseBoolean(zoomSupported)) {
	    	return;
		}

		int tenDesiredZoom = TEN_DESIRED_ZOOM;

		String maxZoom = p.get("max-zoom");
		if (maxZoom != null) {
			try {
				int tenMaxZoom = (int) (10.0 * Double.parseDouble(maxZoom));
				if (tenDesiredZoom > tenMaxZoom) {
					tenDesiredZoom = tenMaxZoom;
				}
			} catch (NumberFormatException nfe) {
				Log.w(TAG, "Bad max-zoom: " + maxZoom);
			}
		}

		String takingPictureZoomMax = p.get("taking-picture-zoom-max");
		if (takingPictureZoomMax != null) {
			try {
				int tenMaxZoom = Integer.parseInt(takingPictureZoomMax);
				if (tenDesiredZoom > tenMaxZoom) {
					tenDesiredZoom = tenMaxZoom;
				}
			} catch (NumberFormatException nfe) {
				Log.w(TAG, "Bad taking-picture-zoom-max: " + takingPictureZoomMax);
			}
		}

		String motZoomValues = p.get("mot-zoom-values");
		if (motZoomValues != null) {
			tenDesiredZoom = findBestMotZoomValue(motZoomValues, tenDesiredZoom);
		}

		String motZoomStep = p.get("mot-zoom-step");
		if (motZoomStep != null) {
			try {
				double step = Double.parseDouble(motZoomStep.trim());
				int tenZoomStep = (int) (10.0 * step);
				if (tenZoomStep > 1) {
					tenDesiredZoom -= tenDesiredZoom % tenZoomStep;
				}
			} catch (NumberFormatException nfe) {
	        // continue
			}
		}

		// Set zoom. This helps encourage the user to pull back.
		// Some devices like the Behold have a zoom parameter
		if (maxZoom != null || motZoomValues != null) {
			p.set("zoom", String.valueOf(tenDesiredZoom / 10.0));
		}

		// Most devices, like the Hero, appear to expose this zoom parameter.
		// It takes on values like "27" which appears to mean 2.7x zoom
		if (takingPictureZoomMax != null) {
			p.set("taking-picture-zoom", tenDesiredZoom);
		}
	}
	  
	private int findBestMotZoomValue(String stringValues, int tenDesiredZoom) {
		int tenBestValue = 0;
		String[] values = stringValues.split(",");
		for (String v:values){
			v = v.trim();
			double value;
			try {
				value = Double.parseDouble(v);
			} catch (NumberFormatException nfe) {
		        return tenDesiredZoom;
			}
			int tenValue = (int) (10.0 * value);
			if (Math.abs(tenDesiredZoom - value) < Math.abs(tenDesiredZoom - tenBestValue)) {
				tenBestValue = tenValue;
			}
		}
		return tenBestValue;
	}

	private Point getCameraResolution(Camera.Parameters p, Point sr) {
		String previewSizeValue = p.get("preview-size-values");
		// saw this on Xperia
		if (previewSizeValue == null) {
			previewSizeValue = p.get("preview-size-value");
		}
	    
		Point ret = null;

		if (previewSizeValue != null) {
			Log.d(TAG, "preview-size-values parameter: " + previewSizeValue);
			ret = findBestPreviewSizeValue(previewSizeValue, sr);
		}

		if (ret == null) {
			// Ensure that the camera resolution is a multiple of 8, as the screen may not be.
			ret = new Point((sr.x >> 3) << 3,(sr.y >> 3) << 3);
		}

		return ret;
	}

	private Point findBestPreviewSizeValue(String previewSizeValue, Point sr) {
		int bestX = 0;
		int bestY = 0;
		int diff = Integer.MAX_VALUE;
		String[] values = previewSizeValue.split(",");
	    
		for(String v:values){
			v = v.trim();
			int dimPosition = v.indexOf('x');
			if (dimPosition < 0) {
				Log.w(TAG, "Bad preview-size: " + v);
				continue;
			}

			int vX;
			int vY;
			try {
				vX = Integer.parseInt(v.substring(0, dimPosition));
				vY = Integer.parseInt(v.substring(dimPosition + 1));
			} catch (NumberFormatException nfe) {
				Log.w(TAG, "Bad preview-size: " + v);
				continue;
			}

			int vDiff = Math.abs(vX - sr.x) + Math.abs(vY - sr.y);
			if (vDiff == 0) {
				bestX = vX;
				bestY = vY;
				break;
			} else if (vDiff < diff) {
				bestX = vX;
				bestY = vY;
				diff = vDiff;
			}
		}

		if (bestX > 0 && bestY > 0) {
			return new Point(bestX, bestY);
		}
		return null;
	}

	/**
	* Closes the camera driver if still in use.
	*/
	  
	public void close(){
		if(camera != null) {
			camera.release();
			camera = null;
		} 
	}

	public void startScan(){
		if (camera != null && previewing) {
			if(mDecoder == null){
				mDecoder = new QRScanDecoder(this, (ResultPointCallback)mContext, uiHandler);
			}
			if(useOneShotPreviewCallback) {
				camera.setOneShotPreviewCallback(this);
			} else {
				camera.setPreviewCallback(this);
			}
			camera.autoFocus(this);
			decoding = false;
		}
	}
	
	/**
	* Asks the camera hardware to begin drawing preview frames to the screen.
	*/
	public void startPreview() {
		if (camera != null && !previewing) {
			camera.startPreview();
			previewing = true;
			mfocus = false;
		}
	}

	/**
	* Tells the camera to stop drawing preview frames.
	*/
	public void stopPreview() {
		if (camera != null && previewing) {
			if (!useOneShotPreviewCallback) {
				camera.setPreviewCallback(null);
			}
			mDecoder.quit();
			mDecoder = null;
			camera.stopPreview();
			previewing = false;
			mfocus = false;
		}
	}

	/**
	* Calculates the framing rect which the UI should draw to show the user where to place the
	* barcode. This target helps with alignment as well as forces the user to hold the device
	* far enough away to ensure the image will be in focus.
	*
	* @return The rectangle to draw on screen in window coordinates.
	*/
	public Rect getFramingRect() {
		if (framingRect == null) {
			if (camera == null) {
				return null;
			}
			int width = SR.x /2;
			if (width < MIN_FRAME_WIDTH) {
				width = MIN_FRAME_WIDTH;
			} else if (width > MAX_FRAME_WIDTH) {
				width = MAX_FRAME_WIDTH;
			}
			int height = SR.y /2;
			if (height < MIN_FRAME_HEIGHT) {
				height = MIN_FRAME_HEIGHT;
			} else if (height > MAX_FRAME_HEIGHT) {
				height = MAX_FRAME_HEIGHT;
			}
      
			width = Math.min(width, height);
			height = width;
      
			int leftOffset = (SR.x - width) / 2;
			int topOffset = (SR.y - height) / 2;
			framingRect = new Rect(leftOffset, topOffset, leftOffset + width, topOffset + height);
			Log.d(TAG, "Calculated framing rect: " + framingRect);
		}
		return framingRect;
	}

	/**
	* Like {@link #getFramingRect} but coordinates are in terms of the preview frame,
	* not UI / screen.
 	*/
	public Rect getFramingRectInPreview() {
		if (framingRectInPreview == null) {
			Rect rect = new Rect(getFramingRect());
     
			rect.left = rect.left * CR.y / SR.x;
			rect.right = rect.right * CR.y / SR.x;
			rect.top = rect.top * CR.x / SR.y;
			rect.bottom = rect.bottom * CR.x / SR.y;
			framingRectInPreview = rect;
		}
		return framingRectInPreview;
	}

	public void resetDecoder(){
		decoding = false;
		if(useOneShotPreviewCallback) {
			camera.setOneShotPreviewCallback(this);
		} else {
			camera.setPreviewCallback(this);
		}
	}

	public void autoFocus(){
		camera.autoFocus(this);
	}

	@Override
	public void onPreviewFrame(byte[] data, Camera c) {
		if(mDecoder == null)
			return;
	
		if(decoding)
			return;
	
		if(mfocus && mDecoder.isIdle()){
			if(!useOneShotPreviewCallback)
				camera.setPreviewCallback(null);
	
			decoding = true;
			if(false == mDecoder.process(data, CR.x, CR.y)){
				if(useOneShotPreviewCallback)
					camera.setOneShotPreviewCallback(this);
				decoding = false;
			}
		}else{
			if(useOneShotPreviewCallback)
				camera.setOneShotPreviewCallback(this);
		}
	}

	@Override
	public void onAutoFocus(boolean focus, Camera c) {
		mfocus = focus;
		Message m = uiHandler.obtainMessage(QRScanDecoder.MSG_AUTO_FOCUS);
		uiHandler.sendMessageDelayed(m, 1500);
	}

}
